package org.dreamwork.websocket;

import org.apache.log4j.Logger;
import org.dreamwork.util.StringUtil;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;


/**
 * Created by game on 2017/2/16
 */
public abstract class WebSocketScanner implements ServerApplicationConfig {
    private static Logger logger = Logger.getLogger (WebSocketScanner.class);

/*
    public static final String PROP_WS_PACKAGE_PREFIX = "org.dreamwork.websocket.prefix";
    public static final String PROP_WS_CONFIG_FILE    = "org.dreamwork.websocket.config";
*/

    protected abstract Set<Class<? extends Endpoint>> getSupportedEndpointClasses ();
    protected Set<Class<?>> getSupportedAnnotatedEndpointClasses () {
        return null;
    }

    @Override
    public Set<ServerEndpointConfig> getEndpointConfigs (Set<Class<? extends Endpoint>> endpointClasses) {
        Set<ServerEndpointConfig> set = new HashSet<ServerEndpointConfig> ();
        Set<Class<? extends Endpoint>> filters = getSupportedEndpointClasses ();
        for (Class<? extends Endpoint> type : endpointClasses) {
            if (type.isAnnotationPresent (IWebSocket.class)) {
                int modifier = type.getModifiers ();
                if (Modifier.isAbstract (modifier) || type.isInterface ()) {
                    continue;
                }

                IWebSocket ws = type.getAnnotation (IWebSocket.class);
                String mapping = ws.value ();
                if (StringUtil.isEmpty (mapping) || mapping.charAt (0) != '/') {
                    logger.warn ("Invalid url mapping: " + mapping + ", ignore the class: " + type);
                    continue;
                }

                if (filters.contains (type)) {
                    Class<? extends ServerEndpointConfig.Configurator> typeOfCfg = ws.configurator ();
                    ServerEndpointConfig.Configurator cfg;
                    if (typeOfCfg == WebSocketHttpConfigurator.class) {
                        cfg = new WebSocketHttpConfigurator (
                                ws.httpSession (),
                                ws.header (),
                                ws.servletContext (),
                                ws.parameter ()
                        );
                    } else {
                        cfg = loadConfigurator (typeOfCfg);
                    }
                    ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create (type, mapping);
                    builder.configurator (cfg);
                    ServerEndpointConfig config = builder.build ();
                    set.add (config);
                    if (logger.isDebugEnabled ()) {
                        logger.debug ("got a web socket class: " + type + ", bound as " + ws.value ());
                    }
                }
            }
        }

        return set;
    }

    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses (Set<Class<?>> scanned) {
        Set<Class<?>> set = new HashSet<Class<?>> ();
//        Map<String, Set<String>> map = findFilters ();
//        Set<String> p = map.get ("p"), c = map.get ("c");
/*
        begin:for (Class<?> type : scanned) {
            String name = type.getPackage ().getName ();
            for (String prefix : p) {
                if (name.startsWith (prefix)) {
                    set.add (type);
                    if (logger.isDebugEnabled ()) {
                        logger.debug ("got " + type + ", hit start with: " + prefix);
                    }
                    continue begin;
                }
            }
            name = type.getCanonicalName ();
            if (c.contains (name)) {
                if (logger.isDebugEnabled ()) {
                    logger.debug ("got class: " + type);
                }
                set.add (type);
            }
        }
*/
        Set<Class<?>> filters = getSupportedAnnotatedEndpointClasses ();

        if (filters == null || filters.isEmpty ()) {
            return scanned;
        }

        for (Class<?> type : scanned) {
            if (filters.contains (type)) {
                set.add (type);
            }
        }

        return set;
    }

/*
    private Map<String, Set<String>> findFilters () {
        Map<String, Set<String>> map = new HashMap<String, Set<String>> ();
        Set<String> p = map.put ("p", new HashSet<String> ());
        Set<String> c = map.put ("c", new HashSet<String> ());

        if (System.getProperties ().containsKey (PROP_WS_PACKAGE_PREFIX)) {
            p.add (System.getProperty (PROP_WS_PACKAGE_PREFIX));
        }

        if (System.getProperties ().containsKey (PROP_WS_CONFIG_FILE)) {
            File file = new File (System.getProperty (PROP_WS_CONFIG_FILE));
            if (file.exists () && file.canRead ()) {
                FileReader in = null;
                try {
                    in = new FileReader (file);
                    BufferedReader reader = new BufferedReader (in);
                    String line;
                    while ((line = reader.readLine ()) != null) {
                        line = line.trim ();
                        if (line.length () < 2
                                || line.charAt (0) == '#'     // comments
                                || line.charAt (1) != ':') continue;
                        char type = line.charAt (0);
                        String expr = line.substring (2).trim ();
                        if (type == 'p') {
                            p.add (expr);
                        } else {
                            c.add (expr);
                        }
                    }
                } catch (IOException ex) {
                    throw new RuntimeException (ex);
                } finally {
                    if (in != null) try {
                        in.close ();
                    } catch (IOException ex) {
                        ex.printStackTrace ();
                    }
                }
            }
        }

        return map;
    }
*/

    private ServerEndpointConfig.Configurator loadConfigurator (Class<? extends ServerEndpointConfig.Configurator> type) {
        try {
            return type.newInstance ();
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
    }
}